using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Reflection;
using System.Reflection.Emit;
using System.ComponentModel;
using System.Xml;
using System.Xml.Serialization;
using QiHe.CodeLib;

namespace QiHe.ReliableData
{
    public class Package
    {
        private List<DataType> dataTypes = new List<DataType>();

        public List<DataType> DataTypes
        {
            get { return dataTypes; }
            set { dataTypes = value; }
        }
        private List<EnumType> enumTypes = new List<EnumType>();

        public List<EnumType> EnumTypes
        {
            get { return enumTypes; }
            set { enumTypes = value; }
        }

        private List<object> dataObjects = new List<object>();

        public List<object> DataObjects
        {
            get { return dataObjects; }
            set { dataObjects = value; }
        }

        private static AssemblyBuilder asmBuilder = null;
        private static ModuleBuilder modBuilder = null;

        public static Dictionary<string, Type> Types = new Dictionary<string, Type>();

        static Package()
        {
            InitializeTypes();
        }

        private static void InitializeTypes()
        {
            Types.Clear();
            Types["bool"] = typeof(bool);
            Types["int"] = typeof(int);
            Types["float"] = typeof(float);
            Types["double"] = typeof(double);
            Types["string"] = typeof(string);
            Types["List<string>"] = typeof(List<string>);
        }

        public DataType FindDataType(string name)
        {
            return DataTypes.Find(
                delegate(DataType dataType)
                {
                    return dataType.Name == name;
                });
        }

        public void BuildTypes()
        {
            InitializeTypes();
            GenerateAssemblyAndModule();
            foreach (EnumType enumType in EnumTypes)
            {
                Types[enumType.Name] = BuildEnum(enumType);
            }
            foreach (DataType dataType in DataTypes)
            {
                DefineType(dataType);
            }
            foreach (DataType dataType in DataTypes)
            {
                Types[dataType.Name] = BuildType(dataType);
                Types["List<" + dataType.Name + ">"] = BuildListType(dataType);
            }
        }

        private static Type BuildListType(DataType dataType)
        {
            return TypeHelper.MakeListType(Types[dataType.Name]);
        }

        public void Save(string xmlfile)
        {
            XmlSerializer mySerializer = new XmlSerializer(this.GetType());
            XmlTextWriter writer = new XmlTextWriter(xmlfile, System.Text.Encoding.UTF8);
            writer.Formatting = Formatting.Indented;
            mySerializer.Serialize(writer, this);
            writer.Close();
        }

        private static void GenerateAssemblyAndModule()
        {
            AssemblyName assemblyName = new AssemblyName();
            assemblyName.Name = "DynamicDataTypes";
            AppDomain thisDomain = Thread.GetDomain();
            asmBuilder = thisDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
            modBuilder = asmBuilder.DefineDynamicModule(asmBuilder.GetName().Name);
        }

        static Dictionary<string, TypeBuilder> TypeBuilders = new Dictionary<string, TypeBuilder>();
        private static void DefineType(DataType dataType)
        {
            TypeBuilders[dataType.Name] = modBuilder.DefineType(dataType.Name,
                TypeAttributes.Public | TypeAttributes.Class);
            Types[dataType.Name] = modBuilder.GetType(dataType.Name);
            Types["List<" + dataType.Name + ">"] = BuildListType(dataType);
        }

        private static Type BuildEnum(EnumType enumType)
        {
            EnumBuilder enumBuilder = modBuilder.DefineEnum(enumType.Name,
                TypeAttributes.Public, typeof(int));
            foreach (EnumValue enumValue in enumType.Values)
            {
                enumBuilder.DefineLiteral(enumValue.Name, enumValue.Value);
            }
            if (enumType.IsFlags)
            {
                ConstructorInfo ci = typeof(FlagsAttribute).GetConstructor(TypeBuilder.EmptyTypes);
                CustomAttributeBuilder cab = new CustomAttributeBuilder(ci, new object[] { });
                enumBuilder.SetCustomAttribute(cab);
            }
            return enumBuilder.CreateType();
        }

        private static Type BuildType(DataType dataType)
        {
            TypeBuilder typeBuilder = TypeBuilders[dataType.Name];
            foreach (TypeField field in dataType.Fields)
            {
                Type type = Types[field.FieldType];
                FieldBuilder fieldBuilder = typeBuilder.DefineField(
                    "_" + field.Name.ToLower(),
                    type, FieldAttributes.Private);
                emitProperty(typeBuilder, fieldBuilder, field, type);
            }
            return typeBuilder.CreateType();
        }

        static void emitProperty(TypeBuilder typeBuilder, FieldBuilder fieldBuilder, TypeField field, Type type)
        {
            //define the property first
            string name = field.Name;
            PropertyBuilder pb = typeBuilder.DefineProperty(name, PropertyAttributes.None, type, TypeBuilder.EmptyTypes);

            //now we define the get method for the property
            MethodBuilder getMethod = typeBuilder.DefineMethod("get_" + name, MethodAttributes.Public, type, TypeBuilder.EmptyTypes);
            ILGenerator ilg = getMethod.GetILGenerator();
            ilg.Emit(OpCodes.Ldarg_0);
            ilg.Emit(OpCodes.Ldfld, fieldBuilder);
            ilg.Emit(OpCodes.Ret);

            //now we generate the set method for the property
            MethodBuilder setMethod = typeBuilder.DefineMethod("set_" + name, MethodAttributes.Public, null, new Type[] { type });
            ilg = setMethod.GetILGenerator();
            ilg.Emit(OpCodes.Ldarg_0);
            ilg.Emit(OpCodes.Ldarg_1);
            ilg.Emit(OpCodes.Stfld, fieldBuilder);
            ilg.Emit(OpCodes.Ret);

            //put the get/set methods in with the property
            pb.SetGetMethod(getMethod);
            pb.SetSetMethod(setMethod);

            //if we specified a description, we will now add the DescriptionAttribute to our property
            if (field.Description != null)
            {
                ConstructorInfo ci = typeof(DescriptionAttribute).GetConstructor(new Type[] { typeof(string) });
                CustomAttributeBuilder cab = new CustomAttributeBuilder(ci, new object[] { field.Description });
                pb.SetCustomAttribute(cab);
            }

            //add a CategoryAttribute if specified
            if (field.Category != null)
            {
                ConstructorInfo ci = typeof(CategoryAttribute).GetConstructor(new Type[] { typeof(string) });
                CustomAttributeBuilder cab = new CustomAttributeBuilder(ci, new object[] { field.Category });
                pb.SetCustomAttribute(cab);
            }

            if (field.FieldType == "string")
            {
                if (!String.IsNullOrEmpty(field.ValueSet))
                {
                    ConstructorInfo ci = typeof(TypeConverterAttribute).GetConstructor(new Type[] { typeof(Type) });
                    CustomAttributeBuilder cab = new CustomAttributeBuilder(ci, new object[] { typeof(ValueSetConverter) });
                    pb.SetCustomAttribute(cab);
                }
                else
                {
                    ConstructorInfo ci = typeof(EditorAttribute).GetConstructor(new Type[] { typeof(string), typeof(Type) });
                    CustomAttributeBuilder cab = new CustomAttributeBuilder(ci, new object[] { 
                    "System.ComponentModel.Design.MultilineStringEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
                    typeof(System.Drawing.Design.UITypeEditor) });
                    pb.SetCustomAttribute(cab);
                }
            }
            else if (field.FieldType == "List<string>")
            {
                ConstructorInfo ci = typeof(EditorAttribute).GetConstructor(new Type[] { typeof(string), typeof(Type) });
                CustomAttributeBuilder cab = new CustomAttributeBuilder(ci, new object[] { 
                    "System.Windows.Forms.Design.ListControlStringCollectionEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
                    typeof(System.Drawing.Design.UITypeEditor) });
                pb.SetCustomAttribute(cab);
            }

            switch (field.XmlSerialize)
            {
                case "XmlAttribute":
                    ConstructorInfo ci = typeof(XmlAttributeAttribute).GetConstructor(TypeBuilder.EmptyTypes);
                    CustomAttributeBuilder cab = new CustomAttributeBuilder(ci, new object[] { });
                    pb.SetCustomAttribute(cab);
                    break;
                case "XmlElement":
                    ci = typeof(XmlElementAttribute).GetConstructor(new Type[] { typeof(string) });
                    cab = new CustomAttributeBuilder(ci, new object[] { GetFieldXmlTypeName(field) });
                    pb.SetCustomAttribute(cab);
                    break;
            }
        }

        public static string GetFieldXmlTypeName(TypeField field)
        {
            if (field.XmlSerialize == "XmlElement" && TypeHelper.IsListType(field.FieldType))
            {
                return TypeHelper.GetItemType(field.FieldType);
            }
            else
            {
                return field.Name;
            }
        }
    }
}
